mutable struct ImpulseDict{R, S} <: ResultDict{R, S}
    toplevel::Dict{R, S}
    internals
    T

    function ImpulseDict{R, S}(data::ImpulseDict{R, S}; internals = nothing, T = nothing) where {R, S}
        if !(internals isa Nothing) || !(T isa Nothing)
            error("Supplying ImpulseDict and also internal or T to constructor not allowed")
        end
        _toplevel = copy(data.toplevel)
        _internals = copy(data.internals)
        _T = data.T
        return new{R, S}(_toplevel, _internals, _T)
    end

    function ImpulseDict{R, S}(data::ResultDict{R, S}; internals = nothing, T = nothing) where {R, S}
        if !isa(internals, Nothing)
            error("Supplying $(nameof(typeof(data))) and also internals to constructor not allowed")
        end
        _toplevel = copy(data.toplevel)
        _internals = copy(data.internals)
        _T = (T isa Nothing) ? infer_length(_toplevel) : T
        return new{R, S}(_toplevel, _internals, _T)
    end

    function ImpulseDict{R, S}(data::AbstractDict{R, S}; internals = nothing, T = nothing) where {R, S}
        _toplevel = copy(data)
        _internals = internals isa Nothing ? Dict() : copy(internals)
        _T = (T isa Nothing) ? infer_length(_toplevel) : T
        return new{R, S}(_toplevel, _internals, _T)
    end
end

ImpulseDict(data::AbstractDict{R, S}; internals=nothing, T=nothing) where {R, S} = ImpulseDict{R, S}(data; internals=internals, T=T) 

function infer_length(toplevel::AbstractDict)
    lengths = [length(v) for v ∈ values(toplevel)]
    maxlength = maximum(lengths)
    minlength = minimum(lengths)
    if maxlength != minlength
        error("Building ImpulseDict with inconsistent lengths $(maxlength) and $(minlength)")
    end
    return maxlength
end
infer_length(i::ImpulseDict) = infer_length(i.toplevel)

Base.getindex(i::ImpulseDict, k) = getindex(i, k; T = i.T)
Base.getindex(i::ImpulseDict, k::String) = getindex(i, k; T = i.T)

function unary_operation(i::ImpulseDict, op)
    toplevel = Dict(k => op(v) for (k, v) ∈ i.toplevel)
    internals = Dict()
    for b ∈ keys(i.internals)
        internals[b] = Dict(k => op(v) for (k, v) ∈ i.internals[b])
    end
    return ImpulseDict(toplevel; internals=internals, T=i.T)
end

function binary_operation(a::ImpulseDict, b::Union{ImpulseDict, SteadyStateDict}, op)
    toplevel = Dict(k => op.(v, b[k]) for (k, v) ∈ a.toplevel)
    internals = Dict()
    for i ∈ keys(a.internals)
        b_internals = b.internals[i]
        internals[i] = Dict(k => op.(v, b_internals[k]) for (k, v) ∈ a.internals[i])
    end
    return ImpulseDict(toplevel; internals=internals, T=a.T)
end
function binary_operation(a::ImpulseDict, b::Union{Integer, AbstractFloat}, op)
    toplevel = Dict(k => op(v, b) for (k, v) ∈ a.toplevel)
    internals = Dict()
    for i ∈ a.internals
        internals[i] = Dict(k => op(v, b) for (k, v) ∈ a.internals[i])
    end
    return ImpulseDict(toplevel; internals=internals, T=a.T)
end

Base.:+(i::ImpulseDict) = i
Base.:-(i::ImpulseDict) = unary_operation(i, -) 
Base.abs(i::ImpulseDict) = unary_operation(i, abs) 

for f ∈ (:+, :-, :*, :/)
    @eval Base.$f(i::ImpulseDict, j::ImpulseDict) = binary_operation(i, j, $f) 
    @eval Base.$f(i::ImpulseDict, j) = binary_operation(i, j, $f) 
    @eval Base.$f(i, j::ImpulseDict) = binary_operation(i, j, $f) 
end

function Base.:*(i::ImpulseDict, x::Bijection)
    newi = deepcopy(i)
    newi.toplevel = x * i.toplevel
    return newi
end
Base.:*(x::Bijection, i::ImpulseDict) = i * x

function pack(i::ImpulseDict; force_order=nothing) #added force_order to deal with an ordered dict 
    T = i.T
    bigv = similar([eltype(promote(values(i.toplevel)...)[1])(0)], T*length(i.toplevel))
    if isnothing(force_order)
        for (n, v) ∈ enumerate(values(i.toplevel))
            bigv[(n-1)*T+1:n*T] = v isa AbstractArray ? v : [v]
        end
    else
        for (n, k) ∈ enumerate(force_order)
            bigv[(n-1)*T+1:n*T] = i.toplevel[k] isa AbstractArray ? i.toplevel[k] : [i.toplevel[k]]
        end
    end
    return bigv
end

function unpack(bigv, outputs, T)
    S = T==1 ? eltype(bigv) : typeof(bigv)
    toplevel = Dict{eltype(outputs), S}()
    for (n, o) ∈ enumerate(outputs)
        toplevel[o] = T==1 ? bigv[(n-1)*T+1] : bigv[(n-1)*T+1:n*T]
    end
    return ImpulseDict(toplevel; T=T)
end

Base.get(i::ImpulseDict, k::AbstractString) = Base.get(i.toplevel, k, zeros(i.T))
Base.get(i::ImpulseDict, k) = typeof(i)(Dict(ki => Base.get(i.toplevel, ki, zeros(i.T)) for ki ∈ k); T=i.T)
